﻿using Logistics.Services.Strategy;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Logistics;
using Nop.Services.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;


namespace Logistics.Services
{
    public interface IRouteSerivce
    {
        PlanType Caculate(IEnumerable<LogisticsPackage> shipments, IEnumerable<LogisticsCourier> availableDrivers, CaculateMethod method);

        TravelInformation GetTravelInformation(Address start, IEnumerable<Address> ends);

        Dictionary<string, object> Settings { get; }

        ILogger Logger { get; set; }
    }
   
    public abstract class RouteServceBase : IRouteSerivce
    {
        public ILogger Logger { get; set; }

        public PlanType Caculate(IEnumerable<LogisticsPackage> packages, IEnumerable<LogisticsCourier> availableDrivers, CaculateMethod method)
        {
            if (packages == null || packages.Count() == 0)
            {
                throw new ArgumentException("Packages are empty.","packages");
            }
            
            if (packages.FirstOrDefault().Warehouse == null)
            {
                throw new ArgumentException("Some packages are not associated with a warehouse.", "packages");
            }
            Address warehouse = packages.FirstOrDefault().Warehouse.Address;

            var pkgs = packages.ToList();
            if (!pkgs.TrueForAll(p => p.Warehouse.Address == warehouse))
            {
                throw new NotImplementedException("Currently multiple warehouses are not supported.");
            }

            if (availableDrivers == null || availableDrivers.Count() == 0)
            {
                throw new ArgumentException("Drivers are not available.", "availableDrivers");
            }

            //TODO: Check more package and driver status.

            List<LogisticsLocation> wps = new List<LogisticsLocation>();

            List<LogisticsLocation> pickupLocs = new List<LogisticsLocation>();
            List<LogisticsLocation> desLocs = new List<LogisticsLocation>();

            pkgs.ForEach(p => { 
                if (p.PickUpLocation.Status == LocationStatus.ReadyForPickUp) 
                {
                    pickupLocs.Add(p.PickUpLocation); 
                    // TODO: Verify relationship here
                    //p.DestinationLocation.RelatedLocation = p.PickupLocation;
                }
                // TODO: Verify status here
                if (p.DeliveryLocation.Status == LocationStatus.PendingDropOff || p.DeliveryLocation.Status == LocationStatus.ReadyForDropOff)
                {
                    desLocs.Add(p.DeliveryLocation);
                }
            });

            wps.AddRange(pickupLocs);
            wps.AddRange(desLocs);

            var das = availableDrivers.Select(d => new DriverAssignment() { Driver = d });
            var plan = new PlanType()
            {
                WareHouse = warehouse,
                DriverAssignments = das.ToArray(),
                UnresolvedWaypoints = wps.ToArray()
            };

            Caculate(plan);

            return plan;
        }

        private void Caculate(PlanType plan)
        {

            // TODO: Dynamically create strategys
            List<IStrategy> strategies = new List<IStrategy>();

            strategies.Add(new SameLocalityStrategy());
            strategies.Add(new SimpleTraversalStrategy());
            strategies.Add(new FixGoWaypointsStrategy());

            var ss = strategies.OrderBy(s => s.Priorty);
            
            foreach (var s in ss)
            {
                s.Init(plan, this, settings);
                s.Caculate();
            }
        }

        protected static HttpWebResponse MakeRequest(string requestUrl)
        {
            int tries = 4;
            HttpWebResponse response = null;
            while (tries-- > 0)
            {
                try
                {
                    HttpWebRequest request = WebRequest.Create(requestUrl) as HttpWebRequest;
                    response = request.GetResponse() as HttpWebResponse;
                    if (response.StatusCode != HttpStatusCode.OK)
                    {
                        throw new WebException(String.Format(
                            "Server error (HTTP {0}: {1}).",
                            response.StatusCode,
                            response.StatusDescription));
                    }
                    break;
                }
                catch (WebException ex)
                {
                    HttpWebResponse res = ex.Response as HttpWebResponse;
                    if (res.StatusCode != HttpStatusCode.InternalServerError && tries > 0)
                    {
                        throw;
                    }
                }
                Thread.Sleep(100);
            }

            return response;
        }

        protected void Log(string format, params object[] args)
        {
            if (Logger != null)
            {
                Logger.Information(String.Format(format, args));
            }
        }

        public abstract TravelInformation GetTravelInformation(Address start, IEnumerable<Address> ends);

        private Dictionary<string, object> settings = new Dictionary<string, object>();
        public Dictionary<string, object> Settings
        {
            get
            {
                return settings;
            }
        }
       
    }
}
